import * as THREE from "../../../modules/three/three.module.js";
import {mapColorCssToCode, WorldConfig} from "../lib/CommonConsts.js";
import {circlesToVerts, vertsToCircles} from "../lib/util/ToolMethods.js";
import ClipperTool from "../lib/clipper_js/ClipperTool.js";
import {defaultAreaColorCode, defaultSurfaceColor, defaultSurfaceColorCode} from "../lib/util/StaticMethods.js";

let areaCount = 0;
function setWorkAreaCount(num) {
  if (areaCount <= num) {
    areaCount = num + 1;
  }
}

const refDistance = 2.5;
/**
 * 获取工作区列表 对应的聚光灯参数
 * @param {WorkArea[]} workAreas  工作区列表，这些工作应该是在同一平面的，朝向相同的
 * @return {{pos: THREE.Vector3, angle: number, radius: number}} 聚光灯参数
 */
export function computeSpotLightInfo(workAreas) {
  const box3 = new THREE.Box3()
  workAreas.forEach(workArea => {
    let localBox = workArea.localBox3
    if (!workArea.localBox3) {
      workArea.updateLocalBox()
    }
    box3.expandByPoint(localBox.min)
    box3.expandByPoint(localBox.max)
  })
  const radius = box3.min.distanceTo(box3.max) * 0.5
  const tan = radius / WorldConfig.spray_spot_light_distance
  const angle = Math.atan(tan)
  const facing = workAreas[0].facing
  const boxCenter = box3.getCenter(new THREE.Vector3())
  const matrix = workAreas[0].planeMat
  const worldBoxCenter = boxCenter.clone().applyMatrix4(matrix)
  const pos = new THREE.Vector3().copy(worldBoxCenter).add(facing.clone().multiplyScalar(WorldConfig.spray_spot_light_distance))
  return {pos, angle, radius, target: worldBoxCenter, localCenter: boxCenter}
}

export function createSpotlightTexture(workAreas, center, radius, colors) {
  const canvas = document.createElement('canvas')
  const s = 256
  canvas.width = s
  canvas.height = s
  const context = canvas.getContext( '2d' )
  // context.rect(0, 0, 256, 256)
  // context.fillStyle = '#f00'
  // context.fill()
  workAreas.forEach((area, index) => {
    const vertices = area.normalizeVertices(center, radius)
    const color = colors[index]
    area.drawOnSpotLightTexture(context, vertices, color)
  })
  const texture = new THREE.CanvasTexture(canvas)
  return texture
}

// 纯数据对象，不直接参与渲染，每个工作区 或 不可喷涂区必对应一个WorkArea
export default class WorkArea {
  // 整数
  id = 0
  // 名称
  name = ""
  // 油漆的颜色编码
  colorCode
  /**
   * 位于本地坐标系，是一个不带旋转的二维矩形轮廓
   * [{x: number, y: number}[], number[] (孔的起始顶点索引) ]
   */
  verts
  // 孔洞的面积 或 喷涂面积, 单位为平方米
  area = 0
  /**
   * 工作区的朝向
   * @type {THREE.Vector3}
   */
  facing = new THREE.Vector3()
  // enuFacing = new THREE.Vector3()
  // enuFrame = new THREE.Matrix4()
  /**
   * verts中的顶点 乘以 planeMat 得到世界3d坐标
   * @type {THREE.Matrix4}
   */
  planeMat = new THREE.Matrix4()
  /**
   * @type {THREE.Matrix4}
   */
  planeMatInv = new THREE.Matrix4()
  /**
   * 在本地坐标系中，工作区的包围盒
   * @type {THREE.Box3}
   */
  localBox3 = new THREE.Box3()
  /**
   * 在本地坐标系中，工作区的包围盒; 形变发生前的包围盒
   * @type {THREE.Box3}
   */
  oldLocalBox3 = new THREE.Box3()
  // true 为不可喷涂区域
  isHole = false
  // true 为曲面的局部切平面
  fromSurface = false
  _width = 0
  _height = 0
  /**
   * @type {number[]}
   * @private
   */
  _segments = []
  outestEdges = 0
  above = 0

  constructor(facing, planeMat, planeMatInv) {
    this.id = areaCount++
    this.name = 'area ' + this.id
    if (facing) {
      this.facing.copy(facing)
    }
    if (planeMat) {
      this.planeMat.copy(planeMat)
      if (planeMatInv) {
        this.planeMatInv.copy(planeMatInv)
      } else {
        this.planeMatInv.copy(planeMat).invert()
      }
    }
  }
  initByMat(mat, above) {
    this.facing.setFromMatrixColumn(mat, 2).negate()
    const pos1 = this.facing.clone().multiplyScalar(above)
    const pos0 = new THREE.Vector3().setFromMatrixPosition(mat).add(pos1)
    const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(this.facing, pos0)
    const pos = plane.projectPoint(new THREE.Vector3(0,0,0), new THREE.Vector3())
    this.planeMat.copy(mat).setPosition(pos)
    this.planeMatInv.copy(this.planeMat).invert()

    return this
  }
  initBySubSurface(mat, above, pointsA, pointsB) {
    this.initByMat(mat, above)
    this.above = above
    const [a, b] = pointsA
    const [d, c] = pointsB
    const arr = [a, b, c, d].map(it => {
      const pt = it.clone().applyMatrix4(this.planeMatInv)
      return pt
    })
    this.verts = [arr, []]
    this.colorCode = defaultSurfaceColorCode
    this.fromSurface = true
    return this
  }

  // 返回 true 表示 该工作区不是立面（墙面）
  notWall() {
    return Math.abs(this.facing.z) > 0.1
  }

  cloneArea() {
    const area = new WorkArea(this.facing, this.planeMat, this.planeMatInv)
    area.verts = []
    area.verts[0] = this.verts[0].slice()
    area.verts[1] = this.verts[1].slice()
    if (this.localBox3) {
      area.localBox3 = new THREE.Box3().copy(this.localBox3)
      // area.localObb = new OBB().copy(this.localObb)
    }
    area.isHole = this.isHole
    return area
  }
  copyWithNoVerts(other) {
    this.id = other.id
    this.facing.copy(other.facing)
    this.planeMat.copy(other.planeMat)
    this.planeMatInv.copy(other.planeMatInv)
    this.colorCode = other.colorCode
    this.fromSurface = other.fromSurface
    this.above = other.above
    return this
  }
  /**
   * 返回当前工作区所在的几何平面
   * @return {THREE.Plane}
   */
  createPlane() {
    const p = this.verts[0][0]
    const point = new THREE.Vector3(p.x, p.y, 0).applyMatrix4(this.planeMat)
    const plane = new THREE.Plane().setFromNormalAndCoplanarPoint(this.facing, point)
    return plane
  }

  getLocalContour() {
    const vectors = []
    this.verts[0].forEach(point => {
      vectors.push(new THREE.Vector3(point.x, point.y, 0))
    })
    return vectors;
  }

  updateLocalBox() {
    this.oldLocalBox3.copy(this.localBox3)
    this.localBox3 = new THREE.Box3().setFromPoints(this.getLocalContour())
    // this.initWidthAndHeight()
    // const obb = new OBB()
    // this.localObb = obb
    // this.localBox3.getCenter(obb.center)
    // obb.halfSize.x = this._width
    // obb.halfSize.y = this._height
  }

  // _intRefPoint() {
  //   this.localBox3.getCenter(this._refPoint);
  //   this._refPoint.applyMatrix4(this.planeMat).add(this.facing.clone().multiplyScalar(refDistance))
  //   return this._refPoint;
  // }
  // initEnuFacing(world3d) {
  //   this.updateLocalBox();
  //   this._intRefPoint();
  //   const tmpPoint = this._refPoint.clone();
  //   tmpPoint.applyMatrix4(world3d._meta.toEarchCoordMat);
  //   // cesiumCoord 是地心地固直角坐标系下的坐标
  //   const cesiumCoord = new Cartesian3(tmpPoint.x, tmpPoint.y, tmpPoint.z);
  //   const upCartesian = Ellipsoid.WGS84.geodeticSurfaceNormal(cesiumCoord, new Cartesian3());
  //   // up 可以理解为球面法向
  //   const up = new THREE.Vector3(upCartesian.x, upCartesian.y, upCartesian.z);
  //   // 向量[tmpPoint.x, tmpPoint.y, 0] 是位于赤道平面内的向量，所以[-tmpPoint.y, tmpPoint.x, 0]可以理解为和赤道单位圆向切的向量
  //   // 为本地坐标系(以tmpPoint为原点的ENU标架)下的正东方向
  //   const east = new THREE.Vector3(-tmpPoint.y, tmpPoint.x, 0).normalize();
  //   const matInv = world3d._meta.toEarchCoordMatInv;
  //   // 把 up 和 east两个向量从地心地固直角坐标系 变换到世界坐标系
  //   up.transformDirection(matInv);
  //   east.transformDirection(matInv);
  //   const north = new THREE.Vector3().crossVectors(up, east).normalize();
  //   this.enuFrame.makeBasis(east, north, up).invert();
  //   this.enuFacing.copy(this.facing).transformDirection(this.enuFrame);
  //   return this.enuFacing
  // }

  getEyeInfo() {
    const focus = this.localBox3.getCenter(new THREE.Vector3())
    focus.applyMatrix4(this.planeMat)
    // const position = focus.clone().add(this.facing.clone().multiplyScalar(WorldConfig.eye_to_face_distance))
    const eyeFacing = this.facing.clone().negate()
    return [focus, eyeFacing]
  }

  /**
   * 返回true 表示 worldPos 在工作区所在几何平面内的投影坐标，是包含在工作区里的
   * @param {THREE.Vector3} worldPos
   * @return {boolean}
   */
  containWorldPosition(worldPos) {
    const localPos = worldPos.clone().applyMatrix4(this.planeMatInv)
    // return BoolPolygonsUtil.isPointInPolygon(localPos, this.verts)
    let contour = this.verts[0];
    if (this.verts[1].length) {
      contour = contour.slice(0, this.verts[1][0]);
    }
    // return BoolPolygonsUtil.isPointInOutline(localPos, contour);
    return ClipperTool.pointInContour(localPos, contour)
    // const box2 = new THREE.Box2().copy(this.localBox3);
    // return box2.containsPoint(localPos);
  }
  /**
   * 返回true 表示 localPos 在工作区所在几何平面内的投影坐标，是包含在工作区里的
   * @param {{x: number, y: number}} localPos
   * @return {boolean}
   */
  containLocalPosition(localPos) {
    const circles = vertsToCircles(this.verts)
    if (circles.length === 1) {
      return ClipperTool.pointInContour(localPos, circles[0])
    }
    const holes = circles.slice(1)
    const hole = holes.find(it => ClipperTool.pointInContour(localPos, it))
    if (hole) {
      return false
    }
    return ClipperTool.pointInContour(localPos, circles[0])
  }

  /**
   * @param {THREE.Vector3} worldPos 世界坐标
   * @param {number} w 一次喷涂笔触的宽度
   * @param {number} h 一次喷涂笔触的高度
   * @return {boolean} true 一次喷涂形成的矩形 和 工作区有交集
   */
  intersectsWithSprayRect(worldPos, w, h) {
    const localPos = worldPos.clone().applyMatrix4(this.planeMatInv);
    let contour = this.verts[0];
    if (this.verts[1].length) {
      contour = contour.slice(0, this.verts[1][0]);
    }
    const hw = w * 0.5;
    const hh = h * 0.5;
    const p0 = {x: localPos.x + hw, y: localPos.y + hh};
    const p1 = {x: localPos.x - hw, y: localPos.y + hh};
    const p2 = {x: localPos.x - hw, y: localPos.y - hh};
    const p3 = {x: localPos.x + hw, y: localPos.y - hh};
    const polygonRect = [[p0, p1, p2, p3], []];
    const polygonArea = [contour, []];
    // return BoolPolygonsUtil.isPolygonAintersectsB(polygonArea, polygonRect);
    return ClipperTool.isPolygonAintersectsB(polygonArea, polygonRect);
  }

  /**
   * 计算工作区面积，
   * @reture {number} 单位为平面米
   */
  computeArea() {
    let circles = vertsToCircles(this.verts)
    let area = 0
    circles.forEach((circle, index) => {
      const num = THREE.ShapeUtils.area(circle)
      if (index === 0) {
        area = Math.abs(num)
      } else {
        area -= Math.abs(num)
      }
    })
    this.area = area
    return area
  }

  // 把工作区的最外轮廓 根据圆心和半径转换为 圆形网格上的uv 坐标
  normalizeVertices(center, radius) {
    const vertices = []
    this.verts[0].forEach(coord => {
      const x = (coord.x - center.x) / radius + 0.5
      // 转换为canvas坐标 (0, 1)，使用的是屏幕坐标系，坐标原点位于屏幕左上角
      const y = 1.0 - ((coord.y - center.y) / radius + 0.5)
      vertices.push({x, y})
    })
    return vertices
  }

  // 把工作区图形绘制到纹理上
  drawOnSpotLightTexture(ctx, vertices, cssColorStr) {
    ctx.beginPath()
    const verticesHoles = [vertices, this.verts[1]]
    const circles = vertsToCircles(verticesHoles)
    circles.forEach(circle => {
      circle.forEach((coord, index) => {
        if (index === 0) {
          ctx.moveTo(coord.x, coord.y)
        } else {
          ctx.lineTo(coord.x, coord.y)
        }
      })
      // ctx.lineTo(circle[0].x, circle[0].y)
      ctx.closePath()
    })
    ctx.fillStyle = cssColorStr
    ctx.fill("evenodd"); // 在填充的时候回遵循奇偶环绕规则
  }

  // 目前只支持矩形工作区，所以有必要调用这个方法
  initWidthAndHeight() {
    const {min, max} = this.localBox3
    //      v3 -------- v2, max
    //      |           |
    // min, v0 -------- v1
    // const v0 = {x: min.x, y: min.y}
    // const v1 = {x: max.x, y: min.y}
    // const v2 = {x: max.x, y: max.y}
    // const v3 = {x: min.x, y: max.y}
    // this.verts = [[v0, v1, v2, v3], []]
    this._width = max.x - min.x;
    this._height = max.y - min.y;
  }

  getAreaWidth() {
    return this._width;
  }

  getAreaHeight() {
    return this._height;
  }

  getCenterInWorld() {
    this.updateLocalBox();
    const pos = this.localBox3.getCenter(new THREE.Vector3());
    return pos.applyMatrix4(this.planeMat);
  }

  getOuterShape() {
    let shape = this.verts[0];
    if (this.verts[1].length) {
      shape = shape.slice(0, this.verts[1][0]);
    }
    return shape;
  }

  resetShapeToBoundingRect() {
    this.updateLocalBox();
    const {min, max} = this.localBox3;
    const v0 = { x: min.x, y: min.y };
    const v1 = { x: max.x, y: min.y };
    const v2 = { x: max.x, y: max.y };
    const v3 = { x: min.x, y: max.y };
    this.verts = [[v0, v1, v2, v3], []];
  }

  /**
   * 把工作区转换到新空间， verts很可能变为带旋转的二维矩形，localObb也需要初始化
   * @param {THREE.Matrix4} planeMatNew
   * @param {THREE.Matrix4} planeMatNewInv
   * @param {THREE.Vector3[]} worldPoints 世界空间的坐标
   */
  toOtherSpace(planeMatNew, planeMatNewInv, worldPoints) {
    const localPoints = worldPoints.map(it => it.applyMatrix4(planeMatNewInv))
    const points = localPoints.map((vector) => {
      return { x: vector.x, y: vector.y }
    })
    this.verts = [points, []]
    this.planeMat.copy(planeMatNew)
    this.planeMatInv.copy(planeMatNewInv)
    this.localBox3 = new THREE.Box3().setFromPoints(localPoints)
    this.oldLocalBox3.copy(this.localBox3)
  }
  initSegments() {
    this._segments.length = 0
    const points = this.verts[0]
    const contours = vertsToCircles(this.verts)
    this.outestEdges = contours[0].length
    contours.forEach(contour => {
      contour.forEach((vector, index) => {
        const nextP = contour[(index + 1) % contour.length]
        // this._segments.push([vector, nextP])
        this._segments.push(points.indexOf(vector), points.indexOf(nextP))
      })
    })
    return this._segments
  }
  getCircleIndex(lineOrPointIndex) {
    const holeIndices = this.verts[1]
    if (!holeIndices.length) {
      return 0
    }
    for (let i = 0; i < holeIndices.length; i++) {
      if (lineOrPointIndex < holeIndices[i]) {
        return i
      }
    }
    return holeIndices.length
  }
  updateHoleIndicesByVertAdd(lineIndex) {
    const holeIndices = this.verts[1]
    if (!holeIndices.length) {
      return
    }
    const circleIndex = this.getCircleIndex(lineIndex)
    holeIndices.forEach((num, index) => {
      if (index >= circleIndex) {
        holeIndices[index] = holeIndices[index] + 1
      }
    })
  }
  updateHoleIndicesByVertRemove(pointIndex) {
    const holeIndices = this.verts[1]
    const points = this.verts[0]
    points.splice(pointIndex, 1)
    if (!holeIndices.length) {
      return
    }
    const circleIndex = this.getCircleIndex(pointIndex)
    holeIndices.forEach((num, index) => {
      if (index >= circleIndex) {
        holeIndices[index] = holeIndices[index] - 1
      }
    })
    const circles = vertsToCircles(this.verts)
    if (circles[0].length < 3) {
      this.verts = null
      return;
    }
    const circlesFiltered = circles.filter(it => it.length > 2)
    if (circlesFiltered.length < circles.length) {
      this.verts = circlesToVerts(circlesFiltered)
    }
  }
  setColorByMesh(mesh) {
    if (!this.colorCode) {
      const color = mesh.material.color.getHexString()
      const colorCss = '#' + color.toLowerCase()
      this.colorCode = mapColorCssToCode.get(colorCss)
    }
  }
  toJsonObj() {
    const {id, name, area, above} = this
    const obj = {id, name, area, above}
    obj.color = this.colorCode //|| defaultAreaColorCode
    obj.matrix = this.planeMat.toArray()
    const [points, holeIndices] = this.verts
    const facing = {x: this.facing.x, y: this.facing.y, z: this.facing.z}
    obj.points = points
    obj.holeIndices = holeIndices
    obj.facing = facing
    obj.isHole = this.isHole ? 1 : 0
    obj.fromSurface = this.fromSurface ? 1 : 0
    return obj
  }
  fromJsonObj(jsonObj) {
    this.id = jsonObj.id
    setWorkAreaCount(this.id || 0)
    this.name = jsonObj.name
    this.area = jsonObj.area
    // if (jsonObj.mat) {
    //   const mat = new THREE.Matrix4().fromArray(jsonObj.mat)
    //   const pA = [], pB = []
    //   let arr = jsonObj.pA
    //   for (let i = 0; i < arr.length; i += 3) {
    //     const v = new THREE.Vector3(arr[i], arr[i+1], arr[i+2])
    //     pA.push(v)
    //   }
    //   arr = jsonObj.pB
    //   for (let i = 0; i < arr.length; i += 3) {
    //     const v = new THREE.Vector3(arr[i], arr[i+1], arr[i+2])
    //     pB.push(v)
    //   }
    //   this.initBySubSurface(mat, 0.1, pA, pB)
    // } else {
      this.colorCode = jsonObj.color //|| defaultAreaColorCode
      this.planeMat.fromArray(jsonObj.matrix)
      this.planeMatInv.copy(this.planeMat).invert()
      this.verts = [jsonObj.points, jsonObj.holeIndices]
      this.facing.copy(jsonObj.facing)
      this.isHole = !!jsonObj.isHole
      this.fromSurface = !!jsonObj.fromSurface
      this.above = jsonObj.above || 0
    // }

    this.updateLocalBox()
    this.initWidthAndHeight()
    return this
  }
}
